package com.yuyou.fn.portal.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.github.wens.framework.service.impl.SuperServiceImpl;
import com.github.wens.mybatisplus.examples.Example;
import com.github.wens.mybatisplus.plugins.Page;
import com.github.wens.mybatisplus.toolkit.IdWorker;
import com.yuyou.fn.common.exception.BusException;
import com.yuyou.fn.common.lang.Tuple2;
import com.yuyou.fn.common.rocketmq.Producer;
import com.yuyou.fn.common.util.*;
import com.yuyou.fn.educational.discount.DiscountRegClassInfo;
import com.yuyou.fn.educational.discount.DiscountRegCourseClass;
import com.yuyou.fn.educational.entity.*;
import com.yuyou.fn.educational.service.*;
import com.yuyou.fn.educational.vo.*;
import com.yuyou.fn.portal.entity.OuterOrder;
import com.yuyou.fn.portal.entity.OuterShoppingcart;
import com.yuyou.fn.portal.mapper.OuterOrderMapper;
import com.yuyou.fn.portal.service.IOuterCourseClassService;
import com.yuyou.fn.portal.service.IOuterOrderService;
import com.yuyou.fn.portal.service.IOuterShoppingcartService;
import com.yuyou.fn.portal.vo.*;
import com.yuyou.fn.settlement.entity.MainAccount;
import com.yuyou.fn.settlement.service.IBalanceLockRecordService;
import com.yuyou.fn.settlement.service.IMainAccountService;
import com.yuyou.fn.settlement.service.IPayOrderService;
import com.yuyou.fn.settlement.service.ISettlementService;
import com.yuyou.fn.settlement.vo.PayWay;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.Future;

/**
 *
 * OuterOrder 表数据服务层接口实现类
 *
 */
@Service
public class OuterOrderServiceImpl extends SuperServiceImpl<OuterOrderMapper, OuterOrder,Long> implements IOuterOrderService {

    private final static Logger log = LoggerFactory.getLogger(OuterOrderServiceImpl.class);

    @Resource
    private ICourseOrderService courseOrderService;

    @Resource
    private ICourseClassService courseClassService;

    @Resource
    private IClassExpandDetailService classExpandDetailService;

    @Resource
    private IStudentService studentService;

    @Resource
    private IStudentRegService studentRegService;


    @Resource
    private IOuterShoppingcartService outerShoppingcartService;

    @Resource
    private IOuterCourseClassService outerCourseClassService;


    @Resource
    private Producer producer;

    @Resource
    private IOuterOrderService outerOrderService;

    @Resource
    private ISettlementService settlementService ;

    @Resource
    private IBalanceLockRecordService balanceLockRecordService ;

    @Resource
    private IDiscountCalculateService discountCalculateService;

    @Resource
    private IPayOrderService payOrderService;

    @Resource
    private Redisson redisson ;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICampusService campusService;

    @Resource
    private IDivRegService divRegService;

    @Override
    public Page<OuterOrderDetail> findOuterOrder(Long userId, int pageNo, int pageSize) {
        return findOuterOrder(userId,pageNo,pageSize,null);
    }

    private List<CourseOrder> createCourseOrders(Long outerOrderId , Student student, List<Long> classIds ,Map<Long,Long> classCounselor ) {
        Map<Long,CourseClass> classId2CourseClassMap = BeanUtils.toMap( courseClassService.findByIds(classIds) ,"classId");
        List<ClassExpandDetail> classExpandDetailList= classExpandDetailService.findByIds(classIds);
        if (classExpandDetailList!=null){
            for (ClassExpandDetail classExpandDetail:classExpandDetailList){
                if (classExpandDetail.getOnlineState()!=null && classExpandDetail.getOnlineState()==0){
                    CourseClass courseClass=classId2CourseClassMap.get(classExpandDetail.getClassId());
                    throw new BusException("【"+courseClass.getClassName()+"】不支持网报，请先咨询校区");
                }
            }
        }
        List<CourseOrder> courseOrderList = new ArrayList<>(classIds.size() * 2 );
        //按区域学生生成子订单
        List<RegClassInfoExtVo> regClassInfoExtVoList = new ArrayList<>(classIds.size() * 22 );
        for(Long classId : classIds ){
            CourseClass courseClass =  classId2CourseClassMap.get(classId);
            if(courseClass.getProgressStatus() == 2 ){
                throw new BusException("【"+courseClass.getClassName()+"】已结课");
            }


            RegClassInfoExtVo regClassInfoExtVo = new RegClassInfoExtVo();
            regClassInfoExtVo.setClassId(courseClass.getClassId());
            Tuple2<Integer,Integer> regLectures = availableLectureForReg(courseClass);
            regClassInfoExtVo.setStartAmount(regLectures.getFirst());
            regClassInfoExtVo.setEndAmount(regLectures.getSecond());

            regClassInfoExtVo.setTotalAmount(calFee( courseClass.getStudyingFee() , regClassInfoExtVo.getEndAmount() - regClassInfoExtVo.getStartAmount() + 1 , courseClass.getLectureAmount()  ));
            regClassInfoExtVo.setAreaTeamId(courseClass.getAreaTeamId());
            regClassInfoExtVo.setStudentId(student.getStudentId());
            if(classCounselor != null ){
                regClassInfoExtVo.setCounselorId(classCounselor.get(courseClass.getClassId()));
            }
            regClassInfoExtVoList.add(regClassInfoExtVo);
        }

        Map<Long,List<RegClassInfoExtVo>> areaTeamId2RegClassInfoExtVo =  BeanUtils.toGroup(regClassInfoExtVoList,"areaTeamId");

        for(Map.Entry<Long,List<RegClassInfoExtVo>> entry : areaTeamId2RegClassInfoExtVo.entrySet() ){
            List<RegClassInfoExtVo> regClassInfoExtVos = entry.getValue();
            List<DiscountRegCourseClass> regCourseClassList = buildDiscountRegCourseClasses(classId2CourseClassMap, student , regClassInfoExtVos );
            Map<Long,List<DiscountInfoVo>> studentId2DiscountInfoListMap = discountCalculateService.calOnlineDiscount(entry.getKey(), regCourseClassList);
            List<CourseClass> courseClassList = new ArrayList<>(entry.getValue().size());
            for(RegClassInfoExtVo  regClassInfoExtVo : entry.getValue() ){
                CourseClass courseClass =  classId2CourseClassMap.get(regClassInfoExtVo.getClassId());
                courseClassList.add(courseClass);
            }
            CreateCourseOrderInfo createCourseOrderInfo = new CreateCourseOrderInfo();
            createCourseOrderInfo.setParentOrderId(outerOrderId);
            createCourseOrderInfo.setStudent(student);
            createCourseOrderInfo.setCourseClassList(courseClassList);
            createCourseOrderInfo.setDiscountInfoList(studentId2DiscountInfoListMap.get(student.getStudentId()));
            createCourseOrderInfo.setRegClassInfoList(entry.getValue());
            createCourseOrderInfo.setFrom(1);
            courseOrderList.add( courseOrderService.createOrder(createCourseOrderInfo,null) );
        }

        return courseOrderList ;
    }

    private Tuple2<Integer,Integer> availableLectureForReg(CourseClass courseClass) {

        List<DivRegInfo> divRegInfos = divRegService.calDivRegInfo(courseClass.getClassName(), courseClass.getAreaTeamName(), courseClass.getBusTeamName(), courseClass.getPeriodName(), courseClass.getGradeName(), courseClass.getSubjectName(), courseClass.getLectureAmount(), courseClass.getSegmentNo());

        if(divRegInfos.isEmpty()){
            divRegInfos = Arrays.asList(new DivRegInfo(Math.max(1,courseClass.getCompletedLectureAmount() + 1 ), courseClass.getLectureAmount(),1,true ));
        }

        DivRegInfo available = divRegInfos.get(0);

        if(divRegInfos.size() > 1 && divRegInfos.get(1).isAvailableReg() ){
            available =  divRegInfos.get(1);
        }

        return Tuple2.of( Math.max(available.getStartLectureNo(), courseClass.getCompletedLectureAmount() + 1), available.getEndLectureNo());
    }

    @Override
    @Transactional
    public Long createOuterOrder(Long classId, Long studentId , Long userId , Long counselorId, String channel)  {
        Future<Long> future = Threads.dispatch(()->{
            try{
                outerShoppingcartService.deleteByUserIdAndClassId(userId,classId);
                OuterShoppingcart outerShoppingcart = outerShoppingcartService.addToShoppingCart(classId, userId ,counselorId);
                return outerShoppingcart.getOuterShoppingcartId();
            }catch (Exception e ){
                log.error("添加到购物车失败" , e );
            }
            return null ;
        });
        Long outerShoppingcartId = Threads.get(future) ;
        if(outerShoppingcartId == null ){
            throw new BusException("创建订单失败");
        }
        return createOuterOrderFromShoppingCart(Arrays.asList(outerShoppingcartId) , studentId ,userId );
    }

    @Transactional
    @Override
    public Long createOuterOrderFromShoppingCart(List<Long> outerShoppingCartIds , Long studentId , Long userId  , String channel) {
        if(outerShoppingCartIds == null || userId == null ){
            throw new NullPointerException();
        }

        if(channel == null ){
            channel = "mobile";
        }

        if(!"mobile".equals(channel) && !"pc".equals(channel) ){
            throw new IllegalArgumentException("Invalid channel");
        }


        List<OuterShoppingcart> outerShoppingcartList=outerShoppingcartService.findByIds(outerShoppingCartIds);

        if(outerShoppingcartList.size()<=0) {
            throw new BusException("该购物车不存在");
        }


        List<Long> classIds = BeanUtils.toList(outerShoppingcartList,"classId");
        Student student = studentService.findById(studentId);

        Future<List<ClassInfoVo>> futureOfClassInfoVos = Threads.dispatch(()->getClassInfo( classIds , student)) ;
        OuterOrder outerOrder =new OuterOrder();
        outerOrder.setOuterOrderId(IdWorker.getId());

        BigDecimal totalAmount = new BigDecimal(0);
        BigDecimal payableAmount = new BigDecimal(0);
        List<CourseOrder> courseOrderList = createCourseOrders(outerOrder.getOuterOrderId(),student,classIds ,getClassCounselor(outerShoppingcartList));
        for(CourseOrder courseOrder : courseOrderList ){
            totalAmount = totalAmount.add(courseOrder.getTotalAmount());
            payableAmount = payableAmount.add(courseOrder.getPayableAmount());
        }

        outerOrder.setChannel(channel);
        outerOrder.setCreateTime(TimeUtils.now());
        outerOrder.setUpdateTime(TimeUtils.now());
        outerOrder.setChargingStatus(0);//未缴费
        outerOrder.setPaidAmount(BigDecimal.ZERO);
        outerOrder.setPayableAmount(payableAmount);
        outerOrder.setTotalAmount(totalAmount);
        outerOrder.setUserId(userId);
        outerOrder.setSubOrderIds(StringUtils.join( BeanUtils.toList(courseOrderList,"courseOrderId") ,","));
        outerOrder.setOrderType(0);

        outerOrder.setOrderData(JSONObject.toJSONString(Threads.get(futureOfClassInfoVos)));

        outerOrder.setPayAmountOfBalance(lockBalance(outerOrder.getOuterOrderId(),studentId,outerOrder.getPayableAmount())) ;

        this.insertSelective(outerOrder);

        //清除购物车
        outerShoppingcartService.deleteBatchIds(outerShoppingCartIds);

        return outerOrder.getOuterOrderId();
    }


    private Map<Long, Long> getClassCounselor(List<OuterShoppingcart> outerShoppingcartList) {
        Map<Long,Long> classCounselor=new HashMap<>();
        for (OuterShoppingcart outerShoppingcart : outerShoppingcartList ) {
            Long classId = outerShoppingcart.getClassId();
            classCounselor.put(classId,outerShoppingcart.getCounselorId());//记录课程的推荐人
        }
        return classCounselor;
    }

    private List<ClassInfoVo> getClassInfo(List<Long> classIds , Student student) {
        List<ClassInfoVo> classInfoVoList = new ArrayList<>();
        for (Long classId : classIds ) {
            ClassInfoVo classInfoVo = new ClassInfoVo();
            SimpleCourseClass courseClassResultVo = outerCourseClassService.findSimpleCourseClassByClassId(classId);
            CourseClass courseClass = courseClassService.findById(classId, CourseClass.BASE_FIELDS);
            Tuple2<Integer, Integer> availableLectureForReg = availableLectureForReg(courseClass);
            classInfoVo.setStartLecture(availableLectureForReg.getFirst() );
            classInfoVo.setEndLecture(availableLectureForReg.getSecond());
            classInfoVo.setClassTotalAmount(calFee(courseClassResultVo.getStudyingFee(),classInfoVo.getEndLecture() - classInfoVo.getStartLecture() + 1 , courseClass.getLectureAmount() ));
            classInfoVo.setCourseClass(courseClassResultVo);
            classInfoVo.setStudentList(Arrays.asList(student));

            classInfoVoList.add(classInfoVo);

        }
        return classInfoVoList ;
    }

    @Override
    @Transactional
    public Long createOuterOrder(Long classId, Long studentId , Long userId , Long counselorId ) {
        return createOuterOrder( classId,  studentId , userId, counselorId , null );
    }

    @Override
    @Transactional
    public Long createOuterOrderFromShoppingCart(List<Long> outerShoppingCartIds, Long studentId , Long userId  ) {
        return createOuterOrderFromShoppingCart(outerShoppingCartIds , studentId  , userId  ,null );
    }

    @Override
    public Page<OuterOrderDetail> findOuterOrder(Long userId, int pageNo, int pageSize, List<Integer> chargingStatusList) {

        Example<OuterOrder> example= OuterOrder.createExample();
        // example.selectProperties("classId");
        Example.Criteria criteria=example.createCriteria();
        criteria.andEqualTo("userId",userId);
        if(chargingStatusList != null && chargingStatusList.size() > 0 ){
            criteria.andIn("chargingStatus",chargingStatusList);
        }

        example.orderBy("createTime").desc();
        Page<OuterOrder> page=findPageByExample(example,pageNo,pageSize);
        List<OuterOrder> outerOrderList =page.getRecords();
        List<OuterOrderDetail> list=new ArrayList<OuterOrderDetail>();
        for(OuterOrder outerOrder : outerOrderList){
            OuterOrderDetail orderCourseClass=new OuterOrderDetail();
            String orderData= outerOrder.getOrderData();
            List<ClassInfoVo> classInfoVoList=JSONObject.parseArray(orderData,ClassInfoVo.class);
            for (ClassInfoVo classInfoVo:classInfoVoList){
                SimpleCourseClass courseClassResultVo=classInfoVo.getCourseClass();
                courseClassResultVo=outerCourseClassService.findSimpleCourseClassByClassId(courseClassResultVo.getClassId());
                classInfoVo.setCourseClass(courseClassResultVo);
            }

            BeanUtils.copyProperties(orderCourseClass, outerOrder);
            orderCourseClass.setClassInfoList(classInfoVoList);
            list.add(orderCourseClass);
        }
        Page<OuterOrderDetail> newPage = Utils.newPage(page);
        newPage.setRecords(list);
        newPage.setTotal(page.getTotal());
        return newPage;
    }

    @Override
    public Long countOuterOrder(Long userId, List<Integer> chargingStatusList) {
        Example<OuterOrder> example= OuterOrder.createExample();
        // example.selectProperties("classId");
        Example.Criteria criteria=example.createCriteria();
        criteria.andEqualTo("userId",userId);
        criteria.andIn("chargingStatus",chargingStatusList);
        return countByExample(example);
    }

    @Override
    public OuterOrderDetail findOuterOrder(Long outerOrderId) {
        OuterOrder outerOrder =this.findById(outerOrderId);
        if(outerOrder ==null) throw new  BusException("不存在该订单");
        OuterOrderDetail outerOrderDetail =new OuterOrderDetail();
        String orderData= outerOrder.getOrderData();
        List<ClassInfoVo> classInfoVoList=JSONObject.parseArray(orderData,ClassInfoVo.class);
        for (ClassInfoVo classInfoVo:classInfoVoList){
            SimpleCourseClass courseClassResultVo=classInfoVo.getCourseClass();
            courseClassResultVo = outerCourseClassService.findSimpleCourseClassByClassId(courseClassResultVo.getClassId());
            classInfoVo.setCourseClass(courseClassResultVo);
        }
        BeanUtils.copyProperties(outerOrderDetail, outerOrder);
        outerOrderDetail.setClassInfoList(classInfoVoList);
        return outerOrderDetail;
    }

    @Override
    public List<OuterOrder> findOuterOrder(Integer chargingStatus, Date date) {
        Example<OuterOrder> example= OuterOrder.createExample();
        Example.Criteria criteria=example.createCriteria();
        criteria.andEqualTo("chargingStatus",chargingStatus);
        criteria.andGreaterThan("createTime",date);
        List<OuterOrder> outerOrderList =this.findListByExample(example);


        return outerOrderList;
    }


    @Override
    public List<StudentCourseClass> findStudentCourseClass(Long userId, Long studentId, int pageNo, int pageSize, Integer regStatus) {
        if (userId==null || studentId==null){
            return Collections.EMPTY_LIST;
        }
        StudentRegQueryVo studentRegQueryVo=new StudentRegQueryVo();
        studentRegQueryVo.setStudentId(studentId);
        studentRegQueryVo.setRegStatus(regStatus);
        Page<StudentReg> page=studentRegService.findStudentRegByQuery(studentRegQueryVo,pageNo,pageSize);
        List<StudentReg> studentRegList=page.getRecords();
        List<StudentCourseClass> courseClassResultVoList=new ArrayList<>();
        if (studentRegList!=null){
            for (StudentReg studentReg:studentRegList){
                SimpleCourseClass simpleCourseClass  = outerCourseClassService.findSimpleCourseClassByClassId(studentReg.getClassId());
                if(simpleCourseClass!=null){
                    StudentCourseClass studentCourseClass = new StudentCourseClass();
                    studentCourseClass.setRegId(studentReg.getRegId());
                    BeanUtils.copyProperties(studentCourseClass,simpleCourseClass);
                    studentCourseClass.setPayAmount(studentReg.getPayAmount());
                    courseClassResultVoList.add(studentCourseClass);
                }

            }

        }
        return  courseClassResultVoList;
    }

    @Override
    public List<OuterOrder> findByLessCreateTimeAndStatus(Date date, Integer chargingStatus) {
        Example<OuterOrder> example= OuterOrder.createExample();
        // example.selectProperties("classId");
        Example.Criteria criteria=example.createCriteria();
        criteria.andEqualTo("chargingStatus",chargingStatus);
        criteria.andLessThan("createTime",date);
        List<OuterOrder> outerOrderList =this.findListByExample(example);
        return outerOrderList;
    }

    @Override
    @Transactional
    public void cancelOuterOrder(Long outerOrderId,Integer chargingStatus) {

        if( payOrderService.isPaid(outerOrderId) ){
            throw new BusException("该订单暂时不能取消");
        }

        OuterOrder outerOrder = this.findById(outerOrderId);
        if (outerOrder==null){
            throw new BusException("该订单不存在");
        }
        if (outerOrder.getChargingStatus()!=0){
            throw new BusException("该订单欠费或支付完成，不可取消");
        }

        if(BigDecimals.compare( outerOrder.getPayAmountOfBalance(),BigDecimal.ZERO ) > 0 ){
            CourseOrder courseOrder = courseOrderService.findById(Utils.toLongIds(outerOrder.getSubOrderIds()).get(0),"studentId") ;
            balanceLockRecordService.unlock(courseOrder.getStudentId(),String.valueOf(outerOrderId),outerOrder.getPayAmountOfBalance());
        }

        String courseOrderIds= outerOrder.getSubOrderIds();
        String[] courseOrderArray=courseOrderIds.split(",");
        for (String courseOrderId:courseOrderArray){
            courseOrderService.cancel(Long.valueOf(courseOrderId),null);
        }
        OuterOrder outerOrder1 =new OuterOrder();
        outerOrder1.setChargingStatus(chargingStatus);
        outerOrder1.setOuterOrderId(outerOrder.getOuterOrderId());
        outerOrder1.setUpdateTime(TimeUtils.now());
        outerOrderService.updateSelectiveById(outerOrder1);
    }


    public List<DiscountDetailVo> calDiscountFromShoppingCart(List<Long> outerShoppingCartIds , Long studentId ){


        if(outerShoppingCartIds == null || outerShoppingCartIds.size() == 0   ){
            throw new NullPointerException();
        }

        List<OuterShoppingcart> outerShoppingcartList=outerShoppingcartService.findByIds(outerShoppingCartIds,"studentIds" ,"classId");

        if(outerShoppingcartList.size()<=0) {
            throw new BusException("该购物车不存在");
        }


        List<Long> classIds = BeanUtils.toList(outerShoppingcartList,"classId");
        Map<Long,CourseClass> classId2CourseClassMap = BeanUtils.toMap( courseClassService.findByIds(Utils.unique(classIds)) ,"classId");
        Student student = studentService.findById(studentId);

        List<RegClassInfoExtVo> regClassInfoExtVoList = new ArrayList<>(classIds.size() * 2 );
        for(Long classId : classIds){
            CourseClass courseClass =  classId2CourseClassMap.get(classId);
            RegClassInfoExtVo regClassInfoExtVo = new RegClassInfoExtVo();
            regClassInfoExtVo.setClassId(courseClass.getClassId());
            Tuple2<Integer, Integer> availableLectureForReg = availableLectureForReg(courseClass);
            regClassInfoExtVo.setStartAmount(availableLectureForReg.getFirst());
            regClassInfoExtVo.setEndAmount(availableLectureForReg.getSecond());
            regClassInfoExtVo.setTotalAmount(calFee( courseClass.getStudyingFee() , regClassInfoExtVo.getEndAmount() - regClassInfoExtVo.getStartAmount() + 1 ,courseClass.getLectureAmount() ));
            regClassInfoExtVo.setAreaTeamId(courseClass.getAreaTeamId());
            regClassInfoExtVo.setStudentId(studentId);
            regClassInfoExtVoList.add(regClassInfoExtVo);
        }

        Map<Long,List<RegClassInfoExtVo>> areaTeamId2RegClassInfoExtVo =  BeanUtils.toGroup(regClassInfoExtVoList,"areaTeamId");

        Map<Long,DiscountDetailVo> studentId2DiscountDetailVo = new HashMap<>();
        for(Map.Entry<Long,List<RegClassInfoExtVo>> entry : areaTeamId2RegClassInfoExtVo.entrySet() ){
            List<DiscountRegCourseClass> regCourseClassList = buildDiscountRegCourseClasses(classId2CourseClassMap, student, entry.getValue() );
            Map<Long,List<DiscountInfoVo>> studentId2DiscountInfoListMap = discountCalculateService.calOnlineDiscount(entry.getKey(), regCourseClassList);
            List<DiscountInfoVo> discountInfoVos = studentId2DiscountInfoListMap.get(student.getStudentId());
            Map<Long,RegClassDiscountVo> classId2RegClassDiscountVo = courseOrderService.shareDiscountToEachCourseClass(discountInfoVos, entry.getValue());
            DiscountDetailVo discountDetailVo = studentId2DiscountDetailVo.get(student.getStudentId());
            if(discountDetailVo == null ){
                discountDetailVo = new DiscountDetailVo();
                discountDetailVo.setStudentId(student.getStudentId());
                discountDetailVo.setStudentName(student.getName());
                discountDetailVo.setRegClassDiscounts(new ArrayList<>(20));
                studentId2DiscountDetailVo.put(student.getStudentId(),discountDetailVo);
            }

            for(RegClassDiscountVo regClassDiscountVo : classId2RegClassDiscountVo.values() ){
                RegClassDiscountExt regClassDiscountExt = new RegClassDiscountExt(regClassDiscountVo);
                CourseClass courseClass = classId2CourseClassMap.get(regClassDiscountVo.getClassId());
                regClassDiscountExt.setClassName(courseClass.getClassName());
                regClassDiscountExt.setLevel(courseClass.getLevel());
                regClassDiscountExt.setClassAliasName(courseClass.getClassAliasName());
                discountDetailVo.getRegClassDiscounts().add(regClassDiscountExt);
            }
        }

        //cal total
        List<DiscountDetailVo> discountDetailVos = new ArrayList<>(studentId2DiscountDetailVo.size() * 5 );
        for(Map.Entry<Long,DiscountDetailVo> entry : studentId2DiscountDetailVo.entrySet() ){
            DiscountDetailVo discountDetailVo = entry.getValue();
            discountDetailVo.setDiscountAmount(BigDecimal.ZERO);
            for(RegClassDiscountExt regClassDiscountExt : discountDetailVo.getRegClassDiscounts() ){
                discountDetailVo.setDiscountAmount(discountDetailVo.getDiscountAmount().add(regClassDiscountExt.getTotalDiscountAmount()));
            }
            discountDetailVos.add(discountDetailVo);
        }
        return discountDetailVos ;
    }

    private List<DiscountRegCourseClass> buildDiscountRegCourseClasses(Map<Long, CourseClass> classId2CourseClassMap, Student student , List<RegClassInfoExtVo> regClassInfoExtVos) {
        List<DiscountRegCourseClass> regCourseClassList = new ArrayList<>(10);
        List<DiscountRegClassInfo> discountRegClassInfoList  = new ArrayList<>(regClassInfoExtVos.size());
        for(RegClassInfoExtVo  regClassInfoExtVo : regClassInfoExtVos ){
            CourseClass courseClass = classId2CourseClassMap.get(regClassInfoExtVo.getClassId());
            DiscountRegClassInfo discountRegClassInfo = new DiscountRegClassInfo();
            BeanUtils.copyProperties(discountRegClassInfo,courseClass);

            List<DivRegInfo> divRegInfos = divRegService.calDivRegInfo(courseClass.getClassName(), courseClass.getAreaTeamName(), courseClass.getBusTeamName(), courseClass.getPeriodName(), courseClass.getGradeName(), courseClass.getSubjectName(), courseClass.getLectureAmount(), courseClass.getSegmentNo());

            if(divRegInfos.isEmpty()){
                throw new BusException("分段报读设置有误");
            }

            DivRegInfo willDivRegInfo = null ;
            for(DivRegInfo divRegInfo : divRegInfos ){
                if(regClassInfoExtVo.getStartAmount()>= divRegInfo.getStartLectureNo() && regClassInfoExtVo.getEndAmount() <= divRegInfo.getEndLectureNo() ){
                    willDivRegInfo = divRegInfo ;
                    break;
                }
            }

            if(willDivRegInfo != null ){
                discountRegClassInfo.setFullReg(willDivRegInfo.getStartLectureNo() == regClassInfoExtVo.getStartAmount() && willDivRegInfo.getEndLectureNo() == regClassInfoExtVo.getEndAmount());
                discountRegClassInfo.setRegDivSegment(willDivRegInfo.getDivNo());
            }else{
                discountRegClassInfo.setFullReg(false);
                discountRegClassInfo.setRegDivSegment(1);
            }
            discountRegClassInfo.setStartAmount(regClassInfoExtVo.getStartAmount());
            discountRegClassInfo.setEndAmount(regClassInfoExtVo.getEndAmount());
            discountRegClassInfo.setTotalAmount(regClassInfoExtVo.getTotalAmount());
            discountRegClassInfoList.add(discountRegClassInfo);
        }
        regCourseClassList.add(new DiscountRegCourseClass(student,discountRegClassInfoList));
        return regCourseClassList;
    }


    @Override
    public OuterOrder findOneRecent(Long userId) {
        Example<OuterOrder> example = OuterOrder.createExample();
        example.createCriteria().andEqualTo("userId" ,userId);
        example.orderBy("createTime").desc();
        return findOneByExample(example);
    }

    private BigDecimal calFee(BigDecimal fee ,int regLecture , int  totalLecture){
        return BigDecimals.integer( BigDecimals.calProportion(fee,BigDecimal.valueOf(regLecture),BigDecimal.valueOf(totalLecture) )) ;
    }

    @Override
    @Transactional
    public void onlinePaySuccess(Long payOrderId, Long outerOrderId, BigDecimal amount, String payInfoExt) {

        RLock lock = redisson.getLock(String.format("outerOrder:%d", outerOrderId));
        if (lock.tryLock()) {
            try {
                OuterOrder outerOrder = this.findById(outerOrderId);
                if (outerOrder.getChargingStatus() == 2) {//支付成功不更新
                    return;
                }

                List<PayWay> payWays = new ArrayList<>(2);

                if(BigDecimals.compare(outerOrder.getPayAmountOfBalance(),BigDecimal.ZERO) > 0 ){
                    payWays.add(new PayWay(3,outerOrder.getPayAmountOfBalance(),null));
                    //解锁余额
                    CourseOrder courseOrder = courseOrderService.findById(Utils.toLongIds(outerOrder.getSubOrderIds()).get(0),"studentId") ;
                    balanceLockRecordService.unlock(courseOrder.getStudentId(),String.valueOf(outerOrderId),outerOrder.getPayAmountOfBalance());
                }

                payWays.add(new PayWay(4,amount,payInfoExt ));
                BigDecimal totalPayAmount  = sum(payWays) ;

                PayInfoOfCourseOrder payInfoOfCourseOrder = new PayInfoOfCourseOrder() ;
                payInfoOfCourseOrder.setPayWays(payWays);
                payInfoOfCourseOrder.setPayAmount(totalPayAmount);
                payOuterOrder(outerOrder, payInfoOfCourseOrder);

            } finally {
                lock.unlock();
            }

            payOrderService.updateBusHandleTime(payOrderId);
        }
    }



    private BigDecimal sum(List<PayWay> payWays) {
        BigDecimal sum = BigDecimal.ZERO ;
        for(PayWay payWay : payWays ){
            sum = sum.add(payWay.getAmount());
        }
        return sum ;
    }

    @Transactional
    @Override
    public void confirmPayForOnlyBalance(Long payOrderId) {
        OuterOrder outerOrder = findById(payOrderId);
        if(BigDecimals.compare( outerOrder.getPayableAmount(),outerOrder.getPayAmountOfBalance() ) != 0 ){
            throw new BusException("Illegal access");
        }

        if(BigDecimals.compare(outerOrder.getPayAmountOfBalance(),BigDecimal.ZERO) > 0 ){
            CourseOrder courseOrder = courseOrderService.findById(Utils.toLongIds(outerOrder.getSubOrderIds()).get(0),"studentId") ;
            balanceLockRecordService.unlock(courseOrder.getStudentId(),String.valueOf(outerOrder.getOuterOrderId()),outerOrder.getPayAmountOfBalance());
        }

        PayInfoOfCourseOrder payInfoOfCourseOrder = new PayInfoOfCourseOrder() ;
        payInfoOfCourseOrder.setPayWays(Arrays.asList(new PayWay(3,outerOrder.getPayAmountOfBalance(),null )));
        payInfoOfCourseOrder.setPayAmount(outerOrder.getPayAmountOfBalance());
        payOuterOrder(outerOrder , payInfoOfCourseOrder );
    }

    @Transactional
    @Override
    public void addDiscount(Long outerOrderId, String discountCode, Long discountId) {

        // 1 优惠券码  2 员工优惠码
        if( !discountId.equals( 1l ) && !discountId.equals(2l) && !discountId.equals(3l)  ){
            throw new BusException("Invalid discountId");
        }

        OuterOrder outerOrder = findById(outerOrderId);

        if(outerOrder.getChargingStatus() != 0 ){
            throw new BusException("Invalid chargeStatus");
        }

        ApplyDiscountVo applyDiscountVo = new ApplyDiscountVo();
        applyDiscountVo.setDiscountCode(discountCode);
        applyDiscountVo.setDiscountId(discountId);
        courseOrderService.addCourseOrderDiscount(Long.valueOf(outerOrder.getSubOrderIds().split(",")[0]),applyDiscountVo );

        BigDecimal totalAmount = new BigDecimal(0);
        BigDecimal payableAmount = new BigDecimal(0);
        List<CourseOrder> courseOrderList = courseOrderService.findCourseOrderByParentOrderId(outerOrderId);
        for(CourseOrder courseOrder : courseOrderList ){
            totalAmount = totalAmount.add(courseOrder.getTotalAmount());
            payableAmount = payableAmount.add(courseOrder.getPayableAmount());
        }

        OuterOrder updateOuterOrder = new OuterOrder();
        updateOuterOrder.setOuterOrderId(outerOrderId);
        updateOuterOrder.setPayableAmount(payableAmount);
        updateOuterOrder.setTotalAmount(totalAmount);
        updateOuterOrder.setPayAmountOfBalance(adjustPayAmountOfBalance(outerOrder.getOuterOrderId() , outerOrder.getSubOrderIds() ,payableAmount,outerOrder.getPayAmountOfBalance()));
        updateSelectiveById(updateOuterOrder);
    }

    private BigDecimal adjustPayAmountOfBalance(Long outerOrderId ,String subOrderIds , BigDecimal orderPayableAmount ,  BigDecimal oldPayAmountOfBalance ) {
        List<Long> oids = Utils.toLongIds(subOrderIds);
        if(oids.size() > 1 ){
            throw new BusException("不支持调整余额");
        }
        CourseOrder courseOrder = courseOrderService.findById(oids.get(0), "studentId");
        if( BigDecimals.compare( oldPayAmountOfBalance, BigDecimal.ZERO ) > 0 ){
            balanceLockRecordService.unlock(courseOrder.getStudentId(),String.valueOf(outerOrderId),oldPayAmountOfBalance);
        }
        return lockBalance( outerOrderId , courseOrder.getStudentId() ,orderPayableAmount ) ;
    }

    private BigDecimal lockBalance(Long outerOrderId,Long studentId , BigDecimal orderAmount ) {
        MainAccount mainAccount = settlementService.findMainAccount(studentId);
        if( mainAccount == null || BigDecimals.compare( mainAccount.getBalanceAmount(), BigDecimal.ZERO ) <= 0 ){
            return BigDecimal.ZERO ;
        }
        BigDecimal realBalance = BigDecimals.min(mainAccount.getBalanceAmount(),orderAmount );
        balanceLockRecordService.lock(studentId,String.valueOf(outerOrderId),realBalance,"购买课程(线上)锁定余额");
        return realBalance ;
    }


    private void payOuterOrder(OuterOrder outerOrder, PayInfoOfCourseOrder payInfoOfCourseOrder) {
        //目前OuterOrder跟CourseOrder一一对应的
        CourseOrder courseOrder = courseOrderService.findById(Long.valueOf(outerOrder.getSubOrderIds()));

        courseOrderService.payCourseOrder(courseOrder,payInfoOfCourseOrder);

        outerOrder.setPaidAmount(outerOrder.getPaidAmount().add(payInfoOfCourseOrder.getPayAmount()));
        if (BigDecimals.compare( outerOrder.getPaidAmount(), outerOrder.getPayableAmount() ) < 0){
            outerOrder.setChargingStatus(1);
        } else if (BigDecimals.compare( outerOrder.getPaidAmount(),outerOrder.getPayableAmount() ) >=0) {
            outerOrder.setChargingStatus(2);
        }

        outerOrder.setUpdateTime(TimeUtils.now());
        updateSelectiveById(outerOrder);
        sendPaySuccessEvent(outerOrder);
    }

    private void sendPaySuccessEvent(OuterOrder outerOrder) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("userId", outerOrder.getUserId());
        jsonObject.put("outerOrderId", outerOrder.getOuterOrderId());
        jsonObject.put("paidAmount", outerOrder.getPaidAmount());
        jsonObject.put("chargingStatus", outerOrder.getChargingStatus());
        byte[] bytes = JSONS.toBytes(jsonObject);
        producer.send("order_success_pay_notify", null, bytes);
    }


    @Override
    public String saveLastCampusGeoLocation(long userId) {
        OuterOrder outerOrder=outerOrderService.findOneRecent(userId);
        if (outerOrder!=null && StringUtils.isNotEmpty(outerOrder.getSubOrderIds())){
            Long courseOrderId=Long.parseLong(outerOrder.getSubOrderIds().split(",")[0]);
            StudentReg studentReg=studentRegService.findRegByCourseOrderId(courseOrderId);
            CourseClass courseClass=courseClassService.findById(studentReg.getClassId(),"classId","campusId");
            if (courseClass.getCampusId()!=null){
                Campus campus=campusService.findById(courseClass.getCampusId());
                if (StringUtils.isNotEmpty(campus.getGeoLocation())){
                    redisTemplate.opsForHash().put("lastCampusGeoLocation",userId+"",campus.getGeoLocation());
                }
                return campus.getGeoLocation();
            }
        }

        return null;
    }

    @Override
    @Transactional
    public void refreshOuterOrderBalancePays(long outerOrderId) {
        RLock lock = redisson.getLock(String.format("outerOrder:%d", outerOrderId));
        if (lock.tryLock()){
            try {
                OuterOrder outerOrder=this.findById(outerOrderId);
                if (outerOrder.getChargingStatus()!=null && outerOrder.getChargingStatus()==0){

                    OuterOrder update=new OuterOrder();
                    update.setOuterOrderId(outerOrderId);
                    update.setPayAmountOfBalance(adjustPayAmountOfBalance(outerOrder.getOuterOrderId(),outerOrder.getSubOrderIds(),outerOrder.getPayableAmount(),outerOrder.getPayAmountOfBalance())) ;
                    update.setUpdateTime(TimeUtils.now());
                    this.updateSelectiveById(update);
                }

            }finally {
                lock.unlock();
            }
        }

    }
}